<?php defined('_JEXEC') or die('Restricted access'); ?>
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002, Richard Heyes                                     |
// | All rights reserved.                                                  |
// |                                                                       |
// | Redistribution and use in source and binary forms, with or without    |
// | modification, are permitted provided that the following conditions    |
// | are met:                                                              |
// |                                                                       |
// | o Redistributions of source code must retain the above copyright      |
// |   notice, this list of conditions and the following disclaimer.       |
// | o Redistributions in binary form must reproduce the above copyright   |
// |   notice, this list of conditions and the following disclaimer in the |
// |   documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote      |
// |   products derived from this software without specific prior written  |
// |   permission.                                                         |
// |                                                                       |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
// |                                                                       |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@phpguru.org>                           |
// | Co-Author: Damian Fernandez Sosa <damlists@cnba.uba.ar>               |
// +-----------------------------------------------------------------------+
//
// $Id: POP3.php,v 1.2 2004/12/05 16:34:39 damian Exp $

if(!class_exists('PEAR')) include(dirname(__FILE__).DS. 'pear.php');
if(!class_exists('Net_Socket')) include(dirname(__FILE__).DS. 'socket.php');


/**
*  +----------------------------- IMPORTANT ------------------------------+
*  | Usage of this class compared to native php extensions such as IMAP   |
*  | is slow and may be feature deficient. If available you are STRONGLY  |
*  | recommended to use the php extensions.                               |
*  +----------------------------------------------------------------------+
*
* POP3 Access Class
*
* For usage see the example script
*/

define('NET_POP3_STATE_DISCONNECTED',  1, true);
define('NET_POP3_STATE_AUTHORISATION', 2, true);
define('NET_POP3_STATE_TRANSACTION',   4, true);

class Net_POP3 {

		/*
		* Some basic information about the mail drop
		* garnered from the STAT command
		*
		* @var array
		*/
		var $_maildrop;

		/*
		* Used for APOP to store the timestamp
		*
		* @var string
		*/
		var $_timestamp;

		/*
		* Timeout that is passed to the socket object
		*
		* @var integer
		*/
		var $_timeout = 0;

		/*
		* Socket object
		*
		* @var object
		*/
		var $_socket;

		/*
		* Current state of the connection. Used with the
		* constants defined above.
		*
		* @var integer
		*/
		var $_state;

		/*
		* Hostname to connect to
		*
		* @var string
		*/
		var $_host;

		/*
		* Port to connect to
		*
		* @var integer
		*/
		var $_port;

		/**
		* To allow class debuging
		* @var boolean
		*/
		var $_debug = false;

		//handles _pear object
		var $_pear;


		/**
		* The auth methods this class support
		* @var array
		*/
		//var $supportedAuthMethods=array('DIGEST-MD5', 'CRAM-MD5', 'APOP' , 'PLAIN' , 'LOGIN', 'USER');
		//Disabling DIGEST-MD5 for now
		var $supportedAuthMethods=array( 'CRAM-MD5', 'APOP' , 'PLAIN' , 'LOGIN', 'USER');
		//var $supportedAuthMethods=array( 'CRAM-MD5', 'PLAIN' , 'LOGIN');
		//var $supportedAuthMethods=array( 'PLAIN' , 'LOGIN');


		/**
		* The auth methods this class support
		* @var array
		*/
		var $supportedSASLAuthMethods=array('DIGEST-MD5', 'CRAM-MD5');


		/**
		* The capability response
		* @var array
		*/
		var $_capability;

	 /*
		* Constructor. Sets up the object variables, and instantiates
		* the socket object.
		*
		*/


	function Net_POP3(){
		$this->_timestamp =  ''; // Used for APOP
		$this->_maildrop  =  array();
		 //$this->_timeout   =  3;
		$this->_state     =  NET_POP3_STATE_DISCONNECTED;
		$this->_socket    = new Net_Socket();
		$this->_pear = new PEAR();
		/*
		* Include the Auth_SASL package.  If the package is not available,
		* we disable the authentication methods that depend upon it.
		*/

		if($this->_debug){
			echo "AUTH_SASL NOT PRESENT!\n";
		}
		foreach($this->supportedSASLAuthMethods as $SASLMethod){
			$pos = array_search( $SASLMethod, $this->supportedAuthMethods );
			if($this->_debug){
					echo "DISABLING METHOD $SASLMethod\n";
			}
			unset($this->supportedAuthMethods[$pos]);
		}
	}


	function setTimeOut($timeOut){
		$this->_timeout   =  $timeOut;
	}


		/**
		* Handles the errors the class can find
		* on the server
		*
		* @access private
		* @return PEAR_Error
		*/

	function _raiseError($msg, $code =-1){
		return $this->_pear->raiseError($msg, $code);
	}



	/*
	* Connects to the given host on the given port.
	* Also looks for the timestamp in the greeting
	* needed for APOP authentication
	*
	* @param  string $host Hostname/IP address to connect to
	* @param  string $port Port to use to connect to on host
	* @return bool  Success/Failure
	*/
	function connect($host = 'localhost', $port = 110){
		$this->_host = $host;
		$this->_port = $port;

		$result = $this->_socket->connect($host, $port, false, $this->_timeout);
		if ($result === true) {
			$data = $this->_recvLn();

			if( $this->_checkResponse($data) ){
			// if the response begins with '+OK' ...
//            if (@substr(strtoupper($data), 0, 3) == '+OK') {
				// Check for string matching apop timestamp
				if (preg_match('/<.+@.+>/U', $data, $matches)) {
						$this->_timestamp = $matches[0];
				}
				$this->_maildrop = array();
				$this->_state    = NET_POP3_STATE_AUTHORISATION;

				return true;
			}
		}

		$this->_socket->disconnect();
		return false;
	}

		/*
		* Disconnect function. Sends the QUIT command
		* and closes the socket.
		*
		* @return bool Success/Failure
		*/
		function disconnect(){
				return $this->_cmdQuit();
		}

		/*
		* Performs the login procedure. If there is a timestamp
		* stored, APOP will be tried first, then basic USER/PASS.
		*
		* @param  string $user Username to use
		* @param  string $pass Password to use
		* @param  mixed $apop Whether to try APOP first, if used as string you can select the auth methd to use ( $pop3->login('validlogin', 'validpass', "CRAM-MD5");
		*          Valid methods are: 'DIGEST-MD5','CRAM-MD5','LOGIN','PLAIN','APOP','USER'
		* @return mixed  true on Success/ PEAR_ERROR on error
		*/
		function login($user, $pass, $apop = true){
			if ($this->_state == NET_POP3_STATE_AUTHORISATION) {

				if($this->_pear->isError($ret= $this->_cmdAuthenticate($user , $pass , $apop ) ) ){
						return $ret;
				}
				if( ! $this->_pear->isError($ret)){
						$this->_state = NET_POP3_STATE_TRANSACTION;
						return true;
				}

			}
			return $this->_raiseError('Generic login error' , 1);
		}



		/**
		* Parses the response from the capability command. Stores
		* the result in $this->_capability
		*
		* @access private
		*/
		function _parseCapability()
		{

				if($this->_sendCmd('CAPA')){
						$data = $this->_getMultiline();
				}else {
						// CAPA command not supported, reset data var
						//  to avoid Notice errors of preg_split on an object
						$data = '';
				}
				$data = preg_split('/\r?\n/', $data, -1, PREG_SPLIT_NO_EMPTY);

				for ($i = 0; $i < count($data); $i++) {

						$capa='';
						if (preg_match('/^([a-z,\-]+)( ((.*))|$)$/i', $data[$i], $matches)) {

								$capa=strtolower($matches[1]);
								switch ($capa) {
										case 'implementation':
												$this->_capability['implementation'] = $matches[3];
												break;
										case 'sasl':
												$this->_capability['sasl'] = preg_split('/\s+/', $matches[3]);
												break;
										default :
												$this->_capability[$capa] = $matches[2];
												break;
								}
						}
				}
		}




		/**
		 * Returns the name of the best authentication method that the server
		 * has advertised.
		 *
		 * @param string if !=null,authenticate with this method ($userMethod).
		 *
		 * @return mixed    Returns a string containing the name of the best
		 *                  supported authentication method or a PEAR_Error object
		 *                  if a failure condition is encountered.
		 * @access private
		 * @since  1.0
		 */
		function _getBestAuthMethod($userMethod = null)
		{

/*
			 return 'USER';
			 return 'APOP';
			 return 'DIGEST-MD5';
			 return 'CRAM-MD5';
*/


				$this->_parseCapability();

				//unset($this->_capability['sasl']);

			 if( isset($this->_capability['sasl']) ){
					 $serverMethods=$this->_capability['sasl'];
			 }else{
						$serverMethods=array('USER');
						// Check for timestamp before attempting APOP
						if ($this->_timestamp != null)
						{
								$serverMethods[] = 'APOP';
						}
			 }

				if($userMethod !== null && $userMethod !== true ){
						$methods = array();
						$methods[] = $userMethod;
						return $userMethod;
				}else{
						$methods = $this->supportedAuthMethods;
				}

				if( ($methods != null) && ($serverMethods != null)){

						foreach ( $methods as $method ) {

								if ( in_array( $method , $serverMethods ) ) {
										return $method;
								}
						}
						$serverMethods=implode(',' , $serverMethods );
						$myMethods=implode(',' ,$this->supportedAuthMethods);
						return $this->_raiseError("$method NOT supported authentication method!. This server " .
								"supports these methods: $serverMethods, but I support $myMethods");
				}else{
						return $this->_raiseError("This server don't support any Auth methods");
				}
		}






		/* Handles the authentication using any known method
		 *
		 * @param string The userid to authenticate as.
		 * @param string The password to authenticate with.
		 * @param string The method to use ( if $usermethod == '' then the class chooses the best method (the stronger is the best ) )
		 *
		 * @return mixed  string or PEAR_Error
		 *
		 * @access private
		 * @since  1.0
		 */
		function _cmdAuthenticate($uid , $pwd , $userMethod = null )
		{


				if ( $this->_pear->isError( $method = $this->_getBestAuthMethod($userMethod) ) ) {
						return $method;
				}

				switch ($method) {
						case 'DIGEST-MD5':
								$result = $this->_authDigest_MD5( $uid , $pwd );
								break;
						case 'CRAM-MD5':
								$result = $this->_authCRAM_MD5( $uid , $pwd );
								break;
						case 'LOGIN':
								$result = $this->_authLOGIN( $uid , $pwd );
								break;
						case 'PLAIN':
								$result = $this->_authPLAIN( $uid , $pwd );
								break;
						case 'APOP':
								$result = $this->_cmdApop( $uid , $pwd );
								// if APOP fails fallback to USER auth
								if( $this->_pear->isError( $result ) ){
										//echo "APOP FAILED!!!\n";
										$result=$this->_authUSER( $uid , $pwd );
								}
								break;
						case 'USER':
								$result = $this->_authUSER( $uid , $pwd );
						break;


						default :
								$result = $this->_raiseError( "$method is not a supported authentication method" );
								break;
				}
				return $result;
		}




		 /* Authenticates the user using the USER-PASS method.
		 *
		 * @param string The userid to authenticate as.
		 * @param string The password to authenticate with.
		 *
		 * @return mixed    true on success or PEAR_Error on failure
		 *
		 * @access private
		 * @since  1.0
		 */
		function _authUSER($user, $pass  )
		{
				if ( $this->_pear->isError($ret=$this->_cmdUser($user) ) ){
						return $ret;
				}
				if ( $this->_pear->isError($ret=$this->_cmdPass($pass) ) ){
						return $ret;
				}
				return true;
		}








		 /* Authenticates the user using the PLAIN method.
		 *
		 * @param string The userid to authenticate as.
		 * @param string The password to authenticate with.
		 *
		 * @return array Returns an array containing the response
		 *
		 * @access private
		 * @since  1.0
		 */
		function _authPLAIN($user, $pass  )
		{
				$cmd=sprintf('AUTH PLAIN %s', base64_encode( chr(0) . $user . chr(0) . $pass ) );

				if ( $this->_pear->isError( $ret = $this->_send($cmd) ) ) {
						return $ret;
				}
				if ( $this->_pear->isError( $challenge = $this->_recvLn() ) ){
						return $challenge;
				}
				if( $this->_pear->isError($ret=$this->_checkResponse($challenge) )){
						return $ret;
				}

				return true;
		}



		 /* Authenticates the user using the PLAIN method.
		 *
		 * @param string The userid to authenticate as.
		 * @param string The password to authenticate with.
		 *
		 * @return array Returns an array containing the response
		 *
		 * @access private
		 * @since  1.0
		 */
		function _authLOGIN($user, $pass  )
		{
				$this->_send('AUTH LOGIN');

				if ( $this->_pear->isError( $challenge = $this->_recvLn() ) ) {
						return $challenge;
				}
				if( $this->_pear->isError($ret=$this->_checkResponse($challenge) )){
						return $ret;
				}


				if ( $this->_pear->isError( $ret = $this->_send(sprintf('%s', base64_encode($user))) ) ) {
						return $ret;
				}

				if ( $this->_pear->isError( $challenge = $this->_recvLn() ) ) {
						return $challenge;
				}
				if( $this->_pear->isError($ret=$this->_checkResponse($challenge) )){
						return $ret;
				}

				if ( $this->_pear->isError( $ret = $this->_send(sprintf('%s', base64_encode($pass))) ) ) {
						return $ret;
				}

				if ( $this->_pear->isError( $challenge = $this->_recvLn() ) ) {
						return $challenge;
				}
				return $this->_checkResponse($challenge);
		}





		 /* Authenticates the user using the CRAM-MD5 method.
		 *
		 * @param string The userid to authenticate as.
		 * @param string The password to authenticate with.
		 *
		 * @return array Returns an array containing the response
		 *
		 * @access private
		 * @since  1.0
		 */
		function _authCRAM_MD5($uid, $pwd )
		{
				if ( $this->_pear->isError( $ret = $this->_send( 'AUTH CRAM-MD5' ) ) ) {
						return $ret;
				}

				if ( $this->_pear->isError( $challenge = $this->_recvLn() ) ) {
						return $challenge;
				}
				if( $this->_pear->isError($ret=$this->_checkResponse($challenge) )){
						return $ret;
				}

				// remove '+ '

				$challenge=substr($challenge,2);

				$challenge = base64_decode( $challenge );

				$cram = &Auth_SASL::factory('crammd5');
				$auth_str = base64_encode( $cram->getResponse( $uid , $pwd , $challenge ) );


				if ( $this->_pear->isError($error = $this->_send( $auth_str ) ) ) {
						return $error;
				}
				if ( $this->_pear->isError( $ret = $this->_recvLn() ) ) {
						return $ret;
				}
				//echo "RET:$ret\n";
				return $this->_checkResponse($ret);
		}



		 /* Authenticates the user using the DIGEST-MD5 method.
		 *
		 * @param string The userid to authenticate as.
		 * @param string The password to authenticate with.
		 * @param string The efective user
		 *
		 * @return array Returns an array containing the response
		 *
		 * @access private
		 * @since  1.0
		 */
		function _authDigest_MD5($uid, $pwd)
		{
				if ( $this->_pear->isError( $ret = $this->_send( 'AUTH DIGEST-MD5' ) ) ) {
						return $ret;
				}

				if ( $this->_pear->isError( $challenge = $this->_recvLn() ) ) {
						return $challenge;
				}
				if( $this->_pear->isError($ret=$this->_checkResponse($challenge) )){
						return $ret;
				}

				// remove '+ '
				$challenge=substr($challenge,2);

				$challenge = base64_decode( $challenge );
				$digest = &Auth_SASL::factory('digestmd5');
				$auth_str = base64_encode($digest->getResponse($uid, $pwd, $challenge, "localhost", "pop3" ));

				if ( $this->_pear->isError($error = $this->_send( $auth_str ) ) ) {
						return $error;
				}

				if ( $this->_pear->isError( $challenge = $this->_recvLn() ) ) {
						return $challenge;
				}
				if( $this->_pear->isError($ret=$this->_checkResponse($challenge) )){
						return $ret;
				}
				 /*
				 * We don't use the protocol's third step because POP3 doesn't allow
				 * subsequent authentication, so we just silently ignore it.
				 */

				if ( $this->_pear->isError( $challenge = $this->_send("\r\n") ) ) {
						return $challenge ;
				}

				if ( $this->_pear->isError( $challenge = $this->_recvLn() ) ) {
						return $challenge;
				}

				return $this->_checkResponse($challenge);


		}










		/*
		* Sends the APOP command
		*
		* @param  $user Username to send
		* @param  $pass Password to send
		* @return bool Success/Failure
		*/
		function _cmdApop($user, $pass)
		{
				if ($this->_state == NET_POP3_STATE_AUTHORISATION) {

						if (!empty($this->_timestamp)) {
								if($this->_pear->isError($data = $this->_sendCmd('APOP ' . $user . ' ' . md5($this->_timestamp . $pass)) ) ){
										return $data;
								}
								$this->_state = NET_POP3_STATE_TRANSACTION;
								return true;
						}
				}
				return $this->_raiseError('Not In NET_POP3_STATE_AUTHORISATION State1');
		}

		/*
		* Returns the raw headers of the specified message.
		*
		* @param  integer $msg_id Message number
		* @return mixed   Either raw headers or false on error
		*/
		function getRawHeaders($msg_id)
		{
				if ($this->_state == NET_POP3_STATE_TRANSACTION) {
						return $this->_cmdTop($msg_id, 0);
				}

				return false;
		}

		/*
		* Returns the  headers of the specified message in an
		* associative array. Array keys are the header names, array
		* values are the header values. In the case of multiple headers
		* having the same names, eg Received:, the array value will be
		* an indexed array of all the header values.
		*
		* @param  integer $msg_id Message number
		* @return mixed   Either array of headers or false on error
		*/
		function getParsedHeaders($msg_id)
		{
				if ($this->_state == NET_POP3_STATE_TRANSACTION) {

						$raw_headers = rtrim($this->getRawHeaders($msg_id));

						$raw_headers = preg_replace("/\r\n[ \t]+/", ' ', $raw_headers); // Unfold headers
						$raw_headers = explode("\r\n", $raw_headers);
						foreach ($raw_headers as $value) {
								$name  = strtolower(substr($value, 0, $pos = strpos($value, ':')));
								$value = ltrim(substr($value, $pos + 1));
								if (isset($headers[$name]) AND is_array($headers[$name])) {
										$headers[$name][] = $value;
								} elseif (isset($headers[$name])) {
										$headers[$name] = array($headers[$name], $value);
								} else {
										$headers[$name] = $value;
								}
						}

						return $headers;
				}

				return false;
		}

		/*
		* Returns the body of the message with given message number.
		*
		* @param  integer $msg_id Message number
		* @return mixed   Either message body or false on error
		*/
		function getBody($msg_id)
		{
				if ($this->_state == NET_POP3_STATE_TRANSACTION) {
						$msg = $this->_cmdRetr($msg_id);
						return substr($msg, strpos($msg, "\r\n\r\n")+4);
				}

				return false;
		}

		/*
		* Returns the entire message with given message number.
		*
		* @param  integer $msg_id Message number
		* @return mixed   Either entire message or false on error
		*/
		function getMsg($msg_id)
		{
				if ($this->_state == NET_POP3_STATE_TRANSACTION) {
						return $this->_cmdRetr($msg_id);
				}

				return false;
		}

		/*
		* Returns the size of the maildrop
		*
		* @return mixed Either size of maildrop or false on error
		*/
		function getSize()
		{
				if ($this->_state == NET_POP3_STATE_TRANSACTION) {
						if (isset($this->_maildrop['size'])) {
								return $this->_maildrop['size'];
						} else {
								list(, $size) = $this->_cmdStat();
								return $size;
						}
				}

				return false;
		}

		/*
		* Returns number of messages in this maildrop
		*
		* @return mixed Either number of messages or false on error
		*/
		function numMsg()
		{
				if ($this->_state == NET_POP3_STATE_TRANSACTION) {
						if (isset($this->_maildrop['num_msg'])) {
								return $this->_maildrop['num_msg'];
						} else {
								list($num_msg, ) = $this->_cmdStat();
								return $num_msg;
						}
				}

				return false;
		}

		/*
		* Marks a message for deletion. Only will be deleted if the
		* disconnect() method is called.
		*
		* @param  integer $msg_id Message to delete
		* @return bool Success/Failure
		*/
		function deleteMsg($msg_id)
		{
				if ($this->_state == NET_POP3_STATE_TRANSACTION) {
						return $this->_cmdDele($msg_id);
				}

				return false;
		}

		/*
		* Combination of LIST/UIDL commands, returns an array
		* of data
		*
		* @param  integer $msg_id Optional message number
		* @return mixed Array of data or false on error
		*/
		function getListing($msg_id = null)
		{

				if ($this->_state == NET_POP3_STATE_TRANSACTION) {
						if (!isset($msg_id)){

								$list=array();
								if ($list = $this->_cmdList()) {
										if ($uidl = $this->_cmdUidl()) {
												foreach ($uidl as $i => $value) {
														$list[$i]['uidl'] = $value['uidl'];
												}
										}
										return $list;
								}else{
										return array();
								}
						} else {
								if ($list = $this->_cmdList($msg_id) AND $uidl = $this->_cmdUidl($msg_id)) {
										return array_merge($list, $uidl);
								}
						}
				}

				return false;
		}

		/*
		* Sends the USER command
		*
		* @param  string $user Username to send
		* @return bool  Success/Failure
		*/
		function _cmdUser($user)
		{
				if ($this->_state == NET_POP3_STATE_AUTHORISATION) {
						return $this->_sendCmd('USER ' . $user);
				}
				return $this->_raiseError('Not In NET_POP3_STATE_AUTHORISATION State');
		}


		/*
		* Sends the PASS command
		*
		* @param  string $pass Password to send
		* @return bool  Success/Failure
		*/
		function _cmdPass($pass)
		{
				if ($this->_state == NET_POP3_STATE_AUTHORISATION) {
						return $this->_sendCmd('PASS ' . $pass);
				}
				return $this->_raiseError('Not In NET_POP3_STATE_AUTHORISATION State');
		}


		/*
		* Sends the STAT command
		*
		* @return mixed Indexed array of number of messages and
		*               maildrop size, or false on error.
		*/
		function _cmdStat()
		{
				if ($this->_state == NET_POP3_STATE_TRANSACTION) {
						if(!$this->_pear->isError($data = $this->_sendCmd('STAT'))){
								sscanf($data, '+OK %d %d', $msg_num, $size);
								$this->_maildrop['num_msg'] = $msg_num;
								$this->_maildrop['size']    = $size;

								return array($msg_num, $size);
						}
				}
				return false;
		}


		/*
		* Sends the LIST command
		*
		* @param  integer $msg_id Optional message number
		* @return mixed   Indexed array of msg_id/msg size or
		*                 false on error
		*/
		function _cmdList($msg_id = null)
		{
				$return=array();
				if ($this->_state == NET_POP3_STATE_TRANSACTION) {
						if (!isset($msg_id)) {
								if(!$this->_pear->isError($data = $this->_sendCmd('LIST') )){
										$data = $this->_getMultiline();
										$data = explode("\r\n", $data);
										foreach ($data as $line) {
												if($line !=''){
														sscanf($line, '%s %s', $msg_id, $size);
														$return[] = array('msg_id' => $msg_id, 'size' => $size);
												}
										}
										return $return;
								}
						} else {
								if(!$this->_pear->isError($data = $this->_sendCmd('LIST ' . $msg_id))){
										if($data!=''){
												sscanf($data, '+OK %d %d', $msg_id, $size);
												return array('msg_id' => $msg_id, 'size' => $size);
										}
										return array();
								}
						}
				}


				return false;
		}


		/*
		* Sends the RETR command
		*
		* @param  integer $msg_id The message number to retrieve
		* @return mixed   The message or false on error
		*/
		function _cmdRetr($msg_id)
		{
				if ($this->_state == NET_POP3_STATE_TRANSACTION) {
						if(!$this->_pear->isError($data = $this->_sendCmd('RETR ' . $msg_id) )){
								$data = $this->_getMultiline();
								return $data;
						}
				}

				return false;
		}


		/*
		* Sends the DELE command
		*
		* @param  integer $msg_id Message number to mark as deleted
		* @return bool Success/Failure
		*/
		function _cmdDele($msg_id)
		{
				if ($this->_state == NET_POP3_STATE_TRANSACTION) {
						return $this->_sendCmd('DELE ' . $msg_id);
				}

				return false;
		}


		/*
		* Sends the NOOP command
		*
		* @return bool Success/Failure
		*/
		function _cmdNoop()
		{
				if ($this->_state == NET_POP3_STATE_TRANSACTION) {
						if(!$this->_pear->isError($data = $this->_sendCmd('NOOP'))){
								return true;
						}
				}

				return false;
		}

		/*
		* Sends the RSET command
		*
		* @return bool Success/Failure
		*/
		function _cmdRset()
		{
				if ($this->_state == NET_POP3_STATE_TRANSACTION) {
						if(!$this->_pear->isError($data = $this->_sendCmd('RSET'))){
								return true;
						}
				}

				return false;
		}

		/*
		* Sends the QUIT command
		*
		* @return bool Success/Failure
		*/
		function _cmdQuit()
		{
				$data = $this->_sendCmd('QUIT');
				$this->_state = NET_POP3_STATE_DISCONNECTED;
				$this->_socket->disconnect();

				return (bool)$data;
		}


		/*
		* Sends the TOP command
		*
		* @param  integer  $msg_id    Message number
		* @param  integer  $num_lines Number of lines to retrieve
		* @return mixed Message data or false on error
		*/
		function _cmdTop($msg_id, $num_lines)
		{
				if ($this->_state == NET_POP3_STATE_TRANSACTION) {

						if(!$this->_pear->isError($data = $this->_sendCmd('TOP ' . $msg_id . ' ' . $num_lines))){
								return $this->_getMultiline();
						}
				}

				return false;
		}

		/*
		* Sends the UIDL command
		*
		* @param  integer $msg_id Message number
		* @return mixed indexed array of msg_id/uidl or false on error
		*/
		function _cmdUidl($msg_id = null)
		{
				if ($this->_state == NET_POP3_STATE_TRANSACTION) {

						if (!isset($msg_id)) {
								if(!$this->_pear->isError($data = $this->_sendCmd('UIDL') )){
										$data = $this->_getMultiline();
										$data = explode("\r\n", $data);
										foreach ($data as $line) {
												sscanf($line, '%d %s', $msg_id, $uidl);
												$return[] = array('msg_id' => $msg_id, 'uidl' => $uidl);
										}

										return $return;
								}
						} else {

								$data = $this->_sendCmd('UIDL ' . $msg_id);
								sscanf($data, '+OK %d %s', $msg_id, $uidl);
								return array('msg_id' => $msg_id, 'uidl' => $uidl);
						}
				}

				return false;
		}









		/*
		* Sends a command, checks the reponse, and
		* if good returns the reponse, other wise
		* returns false.
		*
		* @param  string $cmd  Command to send (\r\n will be appended)
		* @return mixed First line of response if successful, otherwise false
		*/
		function _sendCmd($cmd)
		{
				if ($this->_pear->isError($result = $this->_send($cmd) )){
						return $result ;
				}

				if ($this->_pear->isError($data = $this->_recvLn() )){
						return $data;
				}

				if ( strtoupper(substr($data, 0, 3)) == '+OK') {
						return $data;
				}

				if($cmd == 'CAPA'){
					if($this->_debug){
						echo $data.' FROM request : CAPA';
					}
			return false;
				}

				return $this->_raiseError($data);
		}

		/*
		* Reads a multiline reponse and returns the data
		*
		* @return string The reponse.
		*/
		function _getMultiline()
		{
				$data = '';
				while(!$this->_pear->isError($tmp = $this->_recvLn() ) ) {
						if($tmp == '.'){
								return substr($data, 0, -2);
						}
						if (substr($tmp, 0, 2) == '..') {
								$tmp = substr($tmp, 1);
						}
						$data .= $tmp . "\r\n";
				}
				return substr($data, 0, -2);
		}


	 /**
		* Sets the bebug state
		*
		* @param  bool $debug
		* @access public
		* @return void
		*/
		function setDebug($debug=true)
		{
				$this->_debug=$debug;
		}





	 /**
		 * Send the given string of data to the server.
		 *
		 * @param   string  $data       The string of data to send.
		 *
		 * @return  mixed   True on success or a PEAR_Error object on failure.
		 *
		 * @access  private
		 * @since   1.0
		 */
		function _send($data)
		{
				if ($this->_debug) {
						echo "C: $data\n";
				}

				if ($this->_pear->isError($error = $this->_socket->writeLine($data))) {
						return $this->_raiseError('Failed to write to socket: ' . $error->getMessage());
				}
				return true;
		}



		 /**
		 * Receive the given string of data from the server.
		 *
		 * @return  mixed   a line of response on success or a PEAR_Error object on failure.
		 *
		 * @access  private
		 * @since  1.0
		 */
		function _recvLn()
		{
				if ($this->_pear->isError( $lastline = $this->_socket->readLine( 8192 ) ) ) {
						return $this->_raiseError('Failed to write to socket: ' . $this->lastline->getMessage() );
				}
				if($this->_debug){
						// S: means this data was sent by  the POP3 Server
						echo "S:$lastline\n" ;
				}
				return $lastline;
		}

		 /**
		 * Checks de server Response
		 *
		 * @param  string $response the response
		 * @return  mixed   true on success or a PEAR_Error object on failure.
		 *
		 * @access  private
		 * @since  1.3.3
		 */

		function _checkResponse($response)
		{
				if (@substr(strtoupper($response), 0, 3) == '+OK') {
						return true;
				}else{
						if (@substr(strtoupper($response), 0, 4) == '-ERR') {
								return $this->_raiseError($response);
						}else{
								if (@substr(strtoupper($response), 0, 2) == '+ ') {
										return true;
								}
						}

				}
				return $this->_raiseError("Unknown Response ($response)");
		}



}